﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Data;
using System.Globalization;

namespace SpectationClient.SpectralTools {
    public class ESL_Reader: ENVI_FileReader {

        #region constant definition of meta tag names and corresponding variables (all lower case)
        public const string c_spectra_names = "spectra names";
        public const string c_wavelength = "wavelength";
        public const string c_wavelength_units = "wavelength units";

        public Int32 NumberOfSpectra { get { return this.HV.lines; } }
        public Int32 NumberOfBands { get { return this.HV.samples; } }
        //protected static string[] requiredTags = { c_spectra_names, c_wavelength, c_wavelength_units};
        new protected static string[] requiredTags = { };
        public String FileName { get; set; }

        protected struct ESLSpecificHeaderValues {
            public String[] spectra_names;
            public Single[] wavelength;
            public String wavelength_units;
            public int numberOfSpectra;
            public Boolean[] badBandList;
        }

        ESLSpecificHeaderValues SHV = new ESLSpecificHeaderValues();

        #endregion

        public ESL_Reader(FileInfo blob, FileInfo header):base(blob, header) {
            this.FileName = blob.Name;
        }

        public ESL_Reader(Byte[] blob, String header, String fileName=""):base(blob, header) {
            this.FileName = fileName;
        }
        public ESL_Reader(Byte[] blob, Byte[] headerBlob, String fileName=""):base(blob, headerBlob) {
            this.FileName = fileName;
        }
        override protected void setHeader(String header, Int64 lengthBinaryFile) {
            //base.setHeader(header);
            ESL_Reader.ESLSpecificHeaderValues SHV;
            ENVI_FileReader.RequiredHeaderValues HV;
            List<String> errors;
            if(!ESL_Reader.checkHeader(header, lengthBinaryFile, out errors, out HV, out SHV)) {
                throw new Exception(TextHelper.combine(errors, "\n"));
            } else {
                this.HV = HV;
                this.SHV = SHV;
            }
           
        }

        new public static bool isValidFile(String path) {
            FileInfo temp;
            return ESL_Reader.isValidFile(new FileInfo(path), out temp);
        }

        new public static bool isValidFile(FileInfo fi, out FileInfo headerFile) {
            FileInfo binaryFile;
            List<String> errors;
            RequiredHeaderValues HV;
            ESLSpecificHeaderValues SHV;

            if(!ENVI_FileReader.findFileInfos(fi, out binaryFile, out headerFile)) return false;
            String hdrText = File.ReadAllText(headerFile.FullName);
            return ESL_Reader.checkHeader(hdrText, fi.Length, out errors, out HV, out SHV);

        }

        protected static bool checkHeader(String header, Int64 lengthOfBinaryFile, out List<String> errors, out ENVI_FileReader.RequiredHeaderValues HV, out ESL_Reader.ESLSpecificHeaderValues SHV) {
            ENVI_HeaderParser eslParser = new SpectralTools.ENVI_HeaderParser();
            HV = new RequiredHeaderValues();
            SHV = new ESLSpecificHeaderValues();
            
            Dictionary<String, String> tags =  eslParser.parse(header, out errors);
            if(errors.Count > 0) return false;

            //Check standard tags

            if(!ENVI_FileReader.checkHeader(tags, lengthOfBinaryFile, out errors, out HV)) return false;
            if(errors.Count > 0) return false;

            //Check ESL specific tags
            List<String> missing = (from tag in ESL_Reader.requiredTags
                                    where !tags.Keys.Contains(tag)
                                    select tag).ToList();
            if(missing.Count > 0) {
                errors.Add(String.Format("Undefined meta tags:{0}", TextHelper.combine(missing, ",")));
                return false;
            }

            //Check if required tags can get parsed
            foreach(String tagName in tags.Keys) {
                String tagValueString = tags[tagName];
                try {
                    switch(tagName) {
                        case c_spectra_names: SHV.spectra_names = ENVI_FileReader.ParseStringArray(tagValueString); break;
                        case c_wavelength: SHV.wavelength = ENVI_FileReader.ParseSingleArray(tagValueString); break;
                        case c_wavelength_units: SHV.wavelength_units = tagValueString.Trim(); break;
                        case c_bbl: SHV.badBandList = ENVI_FileReader.ParseBooleanArray(tagValueString); break;
                    }
                } catch {
                    errors.Add(String.Format("Unable to read meta tag {0}", tagName));
                }
            }
            if(errors.Count > 0) return false;
            //check file size
            Int32 expectedSize = HV.headerOffset +  (HV.samples * HV.lines * HV.bands * HV.dataTypeByteSize);
            if(expectedSize != lengthOfBinaryFile) {
                errors.Add(
                    String.Format("Expected size of binary file/blob is {0} bytes instead of {1}",
                                expectedSize, lengthOfBinaryFile)
                    );
            }

            //Check was successful in case no error occured
            return errors.Count == 0;
        } 

        public List<Spectrum> readSpectra() {
            Int32 nSpectra = this.NumberOfSpectra;
            Int32 nBands = this.NumberOfBands;
            List<Spectrum> spectra = new List<Spectrum>();
            for(int i= 0; i < nSpectra; i++) {
                spectra.Add(readSpectrum(i));
                
            }
            return spectra;
        }

        public Spectrum readSpectrum(int index) {
 
            String name = SHV.spectra_names != null && SHV.spectra_names.Length > 0 ? SHV.spectra_names[index].Trim() : String.Format("Spectrum {0}", index+1);
            Spectrum spec = new Spectrum(name);
            BR.setPosition(index * this.NumberOfBands, false);
            spec.BandValues = BR.ReadArray<Single>(this.NumberOfBands);
            
            spec.Bands = SHV.wavelength;
            spec.FileName = FileName;
            spec.FileType = Spectrum.SpectrumFileType.ESL;
            spec.DataType = ENVI_FileReader.getCLIDataType(HV.idlDataType);
            if(SHV.badBandList != null) spec.BadBands = SHV.badBandList;
            String wlu =  SHV.wavelength_units;
            spec.WaveLengthUnit = String.IsNullOrWhiteSpace(wlu) ? "unknown" : SHV.wavelength_units;
            return spec;
        }

      

    }
}
